package com.qyer.search.model.lastminute;

import static com.qyer.commons.utils.CommonUtils.cast2Int;
import static com.qyer.commons.utils.CommonUtils.cast2String;
import static com.qyer.search.model.QyerPlace.buildCity;
import static com.qyer.search.model.QyerPlace.buildCountry;
import static com.qyer.search.model.lastminute.Tag.buildTag;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.qyer.search.cache.mutable.MutableCaches;
import com.qyer.search.enums.SKUChannel;
import com.qyer.search.enums.TagType;
import com.qyer.search.filter.AbstractFilter;
import com.qyer.search.filter.AbstractValidator;
import com.qyer.search.model.City;
import com.qyer.search.model.*;
import com.qyer.search.model.Country;
import com.qyer.search.model.SearchParameter;
import com.qyer.search.model.SearchParameterOrigin;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * User: Z J Wu Date: 2016/01/05 Time: 19:01 Package: com.qyer.search.model.lastminute
 */
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY)
@JsonIgnoreProperties(ignoreUnknown = true)
public class RichSKU extends SKU {

  @JsonProperty(FIELD_OUT_ID)
  private int id;
  @JsonProperty(FIELD_OUT_TITLE)
  private String title;
  @JsonProperty(FIELD_OUT_TYPE1)
  private int type1;
  @JsonProperty(FIELD_OUT_TYPE2)
  private int type2;

  @JsonIgnore
  private String version;
  @JsonProperty(FIELD_START_TIME)
  private long start_time;
  @JsonIgnore
  private long end_time;
  @JsonIgnore
  private int supplierid;
  @JsonIgnore
  private List<SetOutPlace> setOutPlaces = new ArrayList<>(7);
  @JsonIgnore
  private List<Channel> channelList = new ArrayList<>(4);

  @JsonIgnore
  private List<Country> destinationCountries = new ArrayList<>(10);
  @JsonIgnore
  private List<City> destinationCities = new ArrayList<>(10);
  @JsonIgnore
  private Set<SKUChannel> channels;
  @JsonIgnore
  private List<Tag> tags = new ArrayList<>(20);
  @JsonProperty(FIELD_OUT_TAGS)
  private List<String> externalizeTagNames = new ArrayList<>(tags.size());

  public static RichSKU build() {
    return new RichSKU();
  }

  public static RichSKU build(SearchParameter sp, Map<String, Object> dataMap) {
    if (MapUtils.isEmpty(dataMap)) {
      return null;
    }
    int id = cast2Int(dataMap.get(FIELD_ID));
    AbstractValidator validator = MutableCaches.getInstance().SKU_AVAILABILITY_VALIDATOR
      .getCacheContent();
    if (!validator.isValid(sp, id)) {
      return null;
    }
    RichSKU sku = build().id(id).title(cast2String(dataMap.get(FIELD_TITLE)))
                         .type1(cast2Int(dataMap.get(FIELD_TYPE1)))
                         .type2(cast2Int(dataMap.get(FIELD_TYPE2)));
    List<Map<String, Object>> listedData = (List<Map<String, Object>>) dataMap.get(FIELD_COUNTRIES);
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        sku.addDestinationCountry(buildCountry(map));
      }
    }
    listedData = (List<Map<String, Object>>) dataMap.get(FIELD_CITIES);
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        sku.addDestinationCity(buildCity(map));
      }
    }
    listedData = (List<Map<String, Object>>) dataMap.get(FIELD_TAGS);
    Tag tag;
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        tag = buildTag(map);
        if (tag == null) {
          continue;
        }
        sku.addTag(tag);
        if (tag.forExternalize()) {
          sku.addExternalizedTag(tag);
        }
      }
    }
    return sku;
  }

  public static RichSKU build(SearchParameterOrigin sp, Map<String, Object> dataMap) {
    if (MapUtils.isEmpty(dataMap)) {
      return null;
    }
    int id = cast2Int(dataMap.get(FIELD_ID));
    AbstractValidator validator = MutableCaches.getInstance().SKU_AVAILABILITY_VALIDATOR
      .getCacheContent();
    if (!validator.isValid(sp, id)) {
      return null;
    }
    RichSKU sku = build().id(id).title(cast2String(dataMap.get(FIELD_TITLE)))
                         .type1(cast2Int(dataMap.get(FIELD_TYPE1)))
                         .type2(cast2Int(dataMap.get(FIELD_TYPE2)));
    List<Map<String, Object>> listedData = (List<Map<String, Object>>) dataMap.get(FIELD_COUNTRIES);
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        sku.addDestinationCountry(buildCountry(map));
      }
    }
    listedData = (List<Map<String, Object>>) dataMap.get(FIELD_CITIES);
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        sku.addDestinationCity(buildCity(map));
      }
    }
    listedData = (List<Map<String, Object>>) dataMap.get(FIELD_TAGS);
    Tag tag;
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        tag = buildTag(map);
        if (tag == null) {
          continue;
        }
        sku.addTag(tag);
        if (tag.forExternalize()) {
          sku.addExternalizedTag(tag);
        }
      }
    }
    return sku;
  }

  public static RichSKU build(SearchParameterOrigin sp, Map<String, Object> dataMap,
                              Map<Integer,Map<Integer,Integer>> placeSearchCensus,
                              Map<Integer,Map<Integer,Integer>> typeSearchCensus) {
    if (MapUtils.isEmpty(dataMap)) {
      return null;
    }
    int id = cast2Int(dataMap.get(FIELD_ID));
    AbstractValidator validator = MutableCaches.getInstance().SKU_AVAILABILITY_VALIDATOR
      .getCacheContent();
    if (!validator.isValid(sp, id)) {
      return null;
    }
    int t1 = cast2Int(dataMap.get(FIELD_TYPE1));
    int t2 = cast2Int(dataMap.get(FIELD_TYPE2));
    if(t1 != 0) {
      if (typeSearchCensus.containsKey(t1)) {
        int tmp = typeSearchCensus.get(t1).get(-1) + 1;
        typeSearchCensus.get(t1).put(-1, tmp);
      }else {
        Map<Integer, Integer> map = new HashMap<>();
        map.put(-1, 1);
        typeSearchCensus.put(t1, map);
      }
    }
    if(t2 != 0) {
      if (typeSearchCensus.containsKey(t2)) {
        int tmp = typeSearchCensus.get(t2).get(-1) + 1;
        typeSearchCensus.get(t2).put(-1, tmp);
      }else {
        Map<Integer, Integer> map = new HashMap<>();
        map.put(-1, 1);
        typeSearchCensus.put(t2, map);
      }
    }

    RichSKU sku = build().id(id).title(cast2String(dataMap.get(FIELD_TITLE)))
                         .type1(t1)
                         .type2(t2);
    List<Map<String, Object>> listedData = (List<Map<String, Object>>) dataMap.get(FIELD_COUNTRIES);
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        sku.addDestinationCountry(buildCountry(map));
      }
    }
    listedData = (List<Map<String, Object>>) dataMap.get(FIELD_CITIES);
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        sku.addDestinationCity(buildCity(map));
      }
    }
    listedData = (List<Map<String, Object>>) dataMap.get(FIELD_TAGS);
    Tag tag;
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        tag = buildTag(map);
        if (tag == null) {
          continue;
        }
        //
        if(tag.getTagType() == TagType.CITY){
          int cityId = BasicSKU.getCityId(tag.getTagName());
          if(cityId != -1){
            if(placeSearchCensus.containsKey(cityId)){
              int tmp = placeSearchCensus.get(cityId).get(-1) + 1;
              placeSearchCensus.get(cityId).put(-1, tmp);
            }else {
              Map<Integer, Integer> mapTmp = new HashMap<>();
              mapTmp.put(-1, 1);
              placeSearchCensus.put(cityId, mapTmp);
            }
          }
        }
        //
        sku.addTag(tag);
        if (tag.forExternalize()) {
          sku.addExternalizedTag(tag);
        }
      }
    }
    return sku;
  }

  public static RichSKU build(SearchParameterOrigin sp, Map<String, Object> dataMap, List<AbstractFilter> filters,
                              Map<Integer,Map<Integer,Integer>> placeSearchCensus,
                              Map<Integer,Map<Integer,Integer>> typeSearchCensus) {
    if (MapUtils.isEmpty(dataMap)) {
      return null;
    }
    int id = cast2Int(dataMap.get(FIELD_ID));
    AbstractValidator validator = MutableCaches.getInstance().SKU_AVAILABILITY_VALIDATOR
      .getCacheContent();
    if (!validator.isValid(sp, id)) {
      return null;
    }
    for(int i = 0; i < filters.size(); i++){
      if(!filters.get(i).isValid(dataMap, sp))
        return null;
    }

    int t1 = cast2Int(dataMap.get(FIELD_TYPE1));
    int t2 = cast2Int(dataMap.get(FIELD_TYPE2));
    if(t1 != 0) {
      if (typeSearchCensus.containsKey(t1)) {
        int tmp = typeSearchCensus.get(t1).get(-1) + 1;
        typeSearchCensus.get(t1).put(-1, tmp);
      }else {
        Map<Integer, Integer> map = new HashMap<>();
        map.put(-1, 1);
        typeSearchCensus.put(t1, map);
      }
    }
    if(t2 != 0) {
      if (typeSearchCensus.containsKey(t2)) {
        int tmp = typeSearchCensus.get(t2).get(-1) + 1;
        typeSearchCensus.get(t2).put(-1, tmp);
      }else {
        Map<Integer, Integer> map = new HashMap<>();
        map.put(-1, 1);
        typeSearchCensus.put(t2, map);
      }
    }
    RichSKU sku = build().id(id).title(cast2String(dataMap.get(FIELD_TITLE)))
                         .type1(t1)
                         .type2(t2)
                         .version(cast2String(dataMap.get(FIELD_VERSION)))
                         .supplierid(cast2Int(dataMap.get(FIELD_SUPPLIERID)))
                         .startTime(cast2Int(dataMap.get(FIELD_START_TIME)))
                         .endTime(cast2Int(dataMap.get(FIELD_END_TIME)));
    List<Map<String, Object>> listedData = (List<Map<String, Object>>) dataMap.get(FIELD_COUNTRIES);
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        sku.addDestinationCountry(buildCountry(map));
      }
    }
    listedData = (List<Map<String, Object>>) dataMap.get(FIELD_CITIES);
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        sku.addDestinationCity(buildCity(map));
      }
    }
    listedData = (List<Map<String, Object>>) dataMap.get(FIELD_TAGS);
    Tag tag;
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        tag = buildTag(map);
        if (tag == null) {
          continue;
        }
        //
        if(tag.getTagType() == TagType.CITY){
          int cityId = BasicSKU.getCityId(tag.getTagName());
          if(cityId != -1){
            if(placeSearchCensus.containsKey(cityId)){
              int tmp = placeSearchCensus.get(cityId).get(-1) + 1;
              placeSearchCensus.get(cityId).put(-1, tmp);
            }else {
              Map<Integer, Integer> mapTmp = new HashMap<>();
              mapTmp.put(-1, 1);
              placeSearchCensus.put(cityId, mapTmp);
            }
          }
        }
        //
        sku.addTag(tag);
        if (tag.forExternalize()) {
          sku.addExternalizedTag(tag);
        }
      }
    }

    listedData = (List<Map<String, Object>>) dataMap.get(FIELD_SOURCE_PLACE);
    List<SetOutPlace> setOutPlaces1 = new ArrayList<>();
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        setOutPlaces1.add(SetOutPlace.buildPlace(map));
      }
    }
    if(setOutPlaces1.size() > 0)
      sku.setSetOutPlaces(setOutPlaces1);

    listedData = (List<Map<String, Object>>) dataMap.get(FIELD_CHANNELS);
    List<Channel> channels1 = new ArrayList<>();
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        channels1.add(Channel.buildChannel(map));
      }
    }
    if(channels1.size() > 0)
      sku.setChannelList(channels1);

    return sku;
  }

  public static RichSKU build(SearchParameterOrigin sp, Map<String, Object> dataMap, List<AbstractFilter> filters) {
    if (MapUtils.isEmpty(dataMap)) {
      return null;
    }
    int id = cast2Int(dataMap.get(FIELD_ID));
    AbstractValidator validator = MutableCaches.getInstance().SKU_AVAILABILITY_VALIDATOR
      .getCacheContent();
    if (!validator.isValid(sp, id)) {
      return null;
    }
    for(int i = 0; i < filters.size(); i++){
      if(!filters.get(i).isValid(dataMap, sp))
        return null;
    }
    RichSKU sku = build().id(id).title(cast2String(dataMap.get(FIELD_TITLE)))
                         .type1(cast2Int(dataMap.get(FIELD_TYPE1)))
                         .type2(cast2Int(dataMap.get(FIELD_TYPE2)))
                         .version(cast2String(dataMap.get(FIELD_VERSION)))
                         .supplierid(cast2Int(dataMap.get(FIELD_SUPPLIERID)))
                         .startTime(cast2Int(dataMap.get(FIELD_START_TIME)))
                         .endTime(cast2Int(dataMap.get(FIELD_END_TIME)));
    List<Map<String, Object>> listedData = (List<Map<String, Object>>) dataMap.get(FIELD_COUNTRIES);
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        sku.addDestinationCountry(buildCountry(map));
      }
    }
    listedData = (List<Map<String, Object>>) dataMap.get(FIELD_CITIES);
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        sku.addDestinationCity(buildCity(map));
      }
    }
    listedData = (List<Map<String, Object>>) dataMap.get(FIELD_TAGS);
    Tag tag;
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        tag = buildTag(map);
        if (tag == null) {
          continue;
        }
        sku.addTag(tag);
        if (tag.forExternalize()) {
          sku.addExternalizedTag(tag);
        }
      }
    }

    listedData = (List<Map<String, Object>>) dataMap.get(FIELD_SOURCE_PLACE);
    List<SetOutPlace> setOutPlaces1 = new ArrayList<>();
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        setOutPlaces1.add(SetOutPlace.buildPlace(map));
      }
    }
    if(setOutPlaces1.size() > 0)
      sku.setSetOutPlaces(setOutPlaces1);

    listedData = (List<Map<String, Object>>) dataMap.get(FIELD_CHANNELS);
    List<Channel> channels1 = new ArrayList<>();
    if (CollectionUtils.isNotEmpty(listedData)) {
      for (Map<String, Object> map : listedData) {
        channels1.add(Channel.buildChannel(map));
      }
    }
    if(channels1.size() > 0)
      sku.setChannelList(channels1);

    return sku;
  }


  public RichSKU id(int id) {
    setId(id);
    return this;
  }

  public RichSKU title(String title) {
    setTitle(title);
    return this;
  }

  public RichSKU type1(int type1) {
    setType1(type1);
    return this;
  }

  public RichSKU type2(int type2) {
    setType2(type2);
    return this;
  }

  public RichSKU supplierid(int supplierid){
    setSupplierid(supplierid);
    return this;
  }

  public RichSKU startTime(long start_time){
    setStart_time(start_time);
    return this;
  }

  public RichSKU endTime(long end_time){
    setEnd_time(end_time);
    return this;
  }

  public RichSKU version(String version){
    setVersion(version);
    return this;
  }

  public RichSKU addDestinationCountry(Country country) {
    this.destinationCountries.add(country);
    return this;
  }

  public RichSKU addDestinationCity(City city) {
    this.destinationCities.add(city);
    return this;
  }

  public RichSKU channels(SKUChannel... channels) {
    if (ArrayUtils.isNotEmpty(channels)) {
      for (SKUChannel channel : channels) {
        this.channels = EnumSet.of(channel);
      }
    }
    return this;
  }

  public RichSKU addTag(Tag tag) {
    this.tags.add(tag);
    return this;
  }

  public RichSKU addExternalizedTag(Tag tag) {
    this.externalizeTagNames.add(tag.getTagName());
    return this;
  }

  public RichSKU() {
  }

  public RichSKU(int id) {
    this.id = id;
  }

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public int getType1() {
    return type1;
  }

  public void setType1(int type1) {
    this.type1 = type1;
  }

  public int getType2() {
    return type2;
  }

  public void setType2(int type2) {
    this.type2 = type2;
  }

  public List<Country> getDestinationCountries() {
    return destinationCountries;
  }

  public void setDestinationCountries(List<Country> destinationCountries) {
    this.destinationCountries = destinationCountries;
  }

  public List<City> getDestinationCities() {
    return destinationCities;
  }

  public void setDestinationCities(List<City> destinationCities) {
    this.destinationCities = destinationCities;
  }

  public Set<SKUChannel> getChannels() {
    return channels;
  }

  public void setChannels(Set<SKUChannel> channels) {
    this.channels = channels;
  }

  public List<Tag> getTags() {
    return tags;
  }

  public void setTags(List<Tag> tags) {
    this.tags = tags;
  }

  public List<String> getExternalizeTagNames() {
    return externalizeTagNames;
  }

  public void setExternalizeTagNames(List<String> externalizeTagNames) {
    this.externalizeTagNames = externalizeTagNames;
  }

  public List<Channel> getChannelList() {
    return channelList;
  }

  public void setChannelList(List<Channel> channelList) {
    this.channelList = channelList;
  }

  public List<SetOutPlace> getSetOutPlaces() {
    return setOutPlaces;
  }

  public void setSetOutPlaces(List<SetOutPlace> setOutPlaces) {
    this.setOutPlaces = setOutPlaces;
  }

  public int getSupplierid() {
    return supplierid;
  }

  public void setSupplierid(int supplierid) {
    this.supplierid = supplierid;
  }

  public long getEnd_time() {
    return end_time;
  }

  public void setEnd_time(long end_time) {
    this.end_time = end_time;
  }

  public long getStart_time() {
    return start_time;
  }

  public void setStart_time(long start_time) {
    this.start_time = start_time;
  }

  public String getVersion() {
    return version;
  }

  public void setVersion(String version) {
    this.version = version;
  }

  @Override public String toString() {
    return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("id", id)
                                                                      .append("title", title)
                                                                      .append("type1", type1)
                                                                      .append("type2", type2)
                                                                      .toString();
  }

  public static String unknownPositionSKU(int skuId) {
    return "SKU(" + skuId + ")-UnknownPosition";
  }

  public static String naSKU(int skuId) {
    return "SKU(" + skuId + ")-NotAvailable";
  }
}
